/**
 * Alipay.com Inc.
 * Copyright (c) 2004-2012 All Rights Reserved.
 */
package com.jse.api;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.SimpleTimeZone;
import java.util.TreeMap;

import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import com.jse.Strings;
import com.jse.json.Json;
import com.jse.json.JsonObject;

/**
 * 
 * @author runzhi
 */
public class Ali {

    /** RSA最大加密明文大小  */
    private static final int MAX_ENCRYPT_BLOCK = 117;

    /** RSA最大解密密文大小   */
    private static final int MAX_DECRYPT_BLOCK = 128;

    /**
     * 获取签名
     * @param requestHolder
     * @return
     */
    public static String getSignatureContent(Map requestHolder) {
        return getSignContent(getSortedMap(requestHolder));
    }

    public static Map<String, String> getSortedMap(Map appParams) {
        Map<String, String> sortedParams = new TreeMap<String, String>();
        if (appParams != null && appParams.size() > 0) {
            sortedParams.putAll(appParams);
        }
        return sortedParams;
    }

    /**
     * 
     * @param sortedParams
     * @return
     */
    public static String getSignContent(Map<String, String> sortedParams) {
        StringBuffer content = new StringBuffer();
        List<String> keys = new ArrayList<String>(sortedParams.keySet());
        Collections.sort(keys);
        int index = 0;
        for (int i = 0; i < keys.size(); i++) {
            String key = keys.get(i);
            String value = sortedParams.get(key);
            if (Strings.isNotEmpty(key)&&Strings.isNotEmpty(value)) {
                content.append((index == 0 ? "" : "&") + key + "=" + value);
                index++;
            }
        }
        return content.toString();
    }

    public static String extractSignContent(String str, int begin) {
        if (str == null) {
            return null;
        }
        int end = -1;
        int size = str.length();
        int j = begin;
        while (j < size && str.charAt(j) != '{') {
            ++j;
        }
        if (j >= size) {
            return null;
        }
        LinkedList<String> braces = new LinkedList<String>();
        for (int i = j; i < size; ++i) {
            if (str.charAt(i) == '{') {
                braces.push("{");
            } else if (str.charAt(i) == '}') {
                braces.pop();
                if (braces.isEmpty()) {
                    end = i + 1;
                    break;
                }
            }
        }
        return str.substring(j, end);
    }

    /**
     *  rsa内容签名
     * 
     * @param content
     * @param privateKey
     * @param enc
     * @return
     * @throws RuntimeException
     */
    public static String rsaSign(String content, String privateKey, String charset,
                                 String signType) throws RuntimeException {

        if ("RSA".equals(signType)) {

            return rsaSign(content, privateKey, charset);
        } else if ("RSA2".equals(signType)) {

            return rsa256Sign(content, privateKey, charset);
        } else {

            throw new RuntimeException("Sign Type is Not Support : signType=" + signType);
        }

    }

    /**
     * sha256WithRsa 加签
     * 
     * @param content
     * @param privateKey
     * @param enc
     * @return
     * @throws RuntimeException
     */
    public static String rsa256Sign(String content, String privateKey,
                                    String charset) throws RuntimeException {

        try {
            PrivateKey priKey = getPrivateKeyFromPKCS8("RSA",
                new ByteArrayInputStream(privateKey.getBytes()));
            java.security.Signature signature = java.security.Signature
                .getInstance("SHA256WithRSA");

            signature.initSign(priKey);

            if (Strings.isEmpty(charset)) {
                signature.update(content.getBytes());
            } else {
                signature.update(content.getBytes(charset));
            }

            byte[] signed = signature.sign();

            return new String(Base64.getEncoder().encode(signed));
        } catch (Exception e) {
            throw new RuntimeException("RSAcontent = " + content + "; enc = " + charset, e);
        }

    }

    /**
     * sha1WithRsa 加签
     * 
     * @param content
     * @param privateKey
     * @param enc
     * @return
     * @throws RuntimeException
     */
    public static String rsaSign(String content, String privateKey,
                                 String charset) throws RuntimeException {
        try {
            PrivateKey priKey = getPrivateKeyFromPKCS8("RSA",
                new ByteArrayInputStream(privateKey.getBytes()));

            java.security.Signature signature = java.security.Signature
                .getInstance("SHA1WithRSA");

            signature.initSign(priKey);

            if (Strings.isEmpty(charset)) {
                signature.update(content.getBytes());
            } else {
                signature.update(content.getBytes(charset));
            }

            byte[] signed = signature.sign();

            return new String(Base64.getEncoder().encode(signed));
        } catch (InvalidKeySpecException ie) {
            throw new RuntimeException("RSA私钥格式不正确，请检查是否正确配置了PKCS8格式的私钥", ie);
        } catch (Exception e) {
            throw new RuntimeException("RSAcontent = " + content + "; enc = " + charset, e);
        }
    }

    public static String rsaSign(Map<String, String> params, String privateKey,
                                 String charset) throws RuntimeException {
        String signContent = getSignContent(params);

        return rsaSign(signContent, privateKey, charset);

    }

    public static PrivateKey getPrivateKeyFromPKCS8(String algorithm,
                                                    InputStream in) throws Exception {
        if (in == null || Strings.isEmpty(algorithm)) {
            return null;
        }

        KeyFactory keyFactory = KeyFactory.getInstance(algorithm);

        byte[] encodedKey = in.readAllBytes();

        encodedKey = Base64.getDecoder().decode(encodedKey);

        return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encodedKey));
    }

    public static String getSignCheckContentV1(Map<String, String> params) {
        if (params == null) {
            return null;
        }

        params.remove("sign");
        params.remove("sign_type");

        StringBuffer content = new StringBuffer();
        List<String> keys = new ArrayList<String>(params.keySet());
        Collections.sort(keys);

        for (int i = 0; i < keys.size(); i++) {
            String key = keys.get(i);
            String value = params.get(key);
            content.append((i == 0 ? "" : "&") + key + "=" + value);
        }

        return content.toString();
    }

    public static String getSignCheckContentV2(Map<String, String> params) {
        if (params == null) {
            return null;
        }

        params.remove("sign");

        StringBuffer content = new StringBuffer();
        List<String> keys = new ArrayList<String>(params.keySet());
        Collections.sort(keys);

        for (int i = 0; i < keys.size(); i++) {
            String key = keys.get(i);
            String value = params.get(key);
            content.append((i == 0 ? "" : "&") + key + "=" + value);
        }

        return content.toString();
    }

    public static boolean rsaCheckV1(Map<String, String> params, String publicKey,
                                     String charset) throws RuntimeException {
        String sign = params.get("sign");
        String content = getSignCheckContentV1(params);

        return rsaCheckContent(content, sign, publicKey, charset);
    }
    
    public static boolean rsaCheckV1(Map<String, String> params, String publicKey,
            String charset,String signType) throws RuntimeException {
		String sign = params.get("sign");
		String content = getSignCheckContentV1(params);
		
		return rsaCheck(content, sign, publicKey, charset,signType);
    }

    public static boolean rsaCheckV2(Map<String, String> params, String publicKey,
                                     String charset) throws RuntimeException {
        String sign = params.get("sign");
        String content = getSignCheckContentV2(params);

        return rsaCheckContent(content, sign, publicKey, charset);
    }
    
    public static boolean rsaCheckV2(Map<String, String> params, String publicKey,
            String charset,String signType) throws RuntimeException {
		String sign = params.get("sign");
		String content = getSignCheckContentV2(params);
		
		return rsaCheck(content, sign, publicKey, charset,signType);
	}

    public static boolean rsaCheck(String content, String sign, String publicKey, String charset,
                                   String signType) throws RuntimeException {

        if ("RSA".equals(signType)) {

            return rsaCheckContent(content, sign, publicKey, charset);

        } else if ("RSA2".equals(signType)) {

            return rsa256CheckContent(content, sign, publicKey, charset);

        } else {

            throw new RuntimeException("Sign Type is Not Support : signType=" + signType);
        }

    }

    public static boolean rsa256CheckContent(String content, String sign, String publicKey,
                                             String charset) throws RuntimeException {
        try {
            PublicKey pubKey = getPublicKeyFromX509("RSA",
                new ByteArrayInputStream(publicKey.getBytes()));

            java.security.Signature signature = java.security.Signature
                .getInstance("SHA256WithRSA");

            signature.initVerify(pubKey);

            if (Strings.isEmpty(charset)) {
                signature.update(content.getBytes());
            } else {
                signature.update(content.getBytes(charset));
            }

            return signature.verify(Base64.getDecoder().decode(sign.getBytes()));
        } catch (Exception e) {
            throw new RuntimeException(
                "RSAcontent = " + content + ",sign=" + sign + ",enc = " + charset, e);
        }
    }

    public static boolean rsaCheckContent(String content, String sign, String publicKey,
                                          String charset) throws RuntimeException {
        try {
            PublicKey pubKey = getPublicKeyFromX509("RSA",
                new ByteArrayInputStream(publicKey.getBytes()));

            java.security.Signature signature = java.security.Signature
                .getInstance("SHA1WithRSA");

            signature.initVerify(pubKey);

            if (Strings.isEmpty(charset)) {
                signature.update(content.getBytes());
            } else {
                signature.update(content.getBytes(charset));
            }

            return signature.verify(Base64.getDecoder().decode(sign.getBytes()));
        } catch (Exception e) {
            throw new RuntimeException(
                "RSAcontent = " + content + ",sign=" + sign + ",enc = " + charset, e);
        }
    }

    public static PublicKey getPublicKeyFromX509(String algorithm,
                                                 InputStream ins) throws Exception {
        KeyFactory keyFactory = KeyFactory.getInstance(algorithm);

        StringWriter writer = new StringWriter();
        InputStreamReader reader=new InputStreamReader(ins);
        char[] buffer = new char[8192 >> 1];
        int amount;
        while ((amount = reader.read(buffer)) >= 0) {
        	writer.write(buffer, 0, amount);
        }
        byte[] encodedKey = writer.toString().getBytes();

        encodedKey = Base64.getDecoder().decode(encodedKey);

        return keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));
    }

    /**
     * 验签并解密
     * <p>
     * <b>目前适用于公众号</b><br>
     * params参数示例：
     * <br>{
     *    <br>biz_content=M0qGiGz+8kIpxe8aF4geWJdBn0aBTuJRQItLHo9R7o5JGhpic/MIUjvXo2BLB++BbkSq2OsJCEQFDZ0zK5AJYwvBgeRX30gvEj6eXqXRt16/IkB9HzAccEqKmRHrZJ7PjQWE0KfvDAHsJqFIeMvEYk1Zei2QkwSQPlso7K0oheo/iT+HYE8aTATnkqD/ByD9iNDtGg38pCa2xnnns63abKsKoV8h0DfHWgPH62urGY7Pye3r9FCOXA2Ykm8X4/Bl1bWFN/PFCEJHWe/HXj8KJKjWMO6ttsoV0xRGfeyUO8agu6t587Dl5ux5zD/s8Lbg5QXygaOwo3Fz1G8EqmGhi4+soEIQb8DBYanQOS3X+m46tVqBGMw8Oe+hsyIMpsjwF4HaPKMr37zpW3fe7xOMuimbZ0wq53YP/jhQv6XWodjT3mL0H5ACqcsSn727B5ztquzCPiwrqyjUHjJQQefFTzOse8snaWNQTUsQS7aLsHq0FveGpSBYORyA90qPdiTjXIkVP7mAiYiAIWW9pCEC7F3XtViKTZ8FRMM9ySicfuAlf3jtap6v2KPMtQv70X+hlmzO/IXB6W0Ep8DovkF5rB4r/BJYJLw/6AS0LZM9w5JfnAZhfGM2rKzpfNsgpOgEZS1WleG4I2hoQC0nxg9IcP0Hs+nWIPkEUcYNaiXqeBc=,
     *    <br>sign=rlqgA8O+RzHBVYLyHmrbODVSANWPXf3pSrr82OCO/bm3upZiXSYrX5fZr6UBmG6BZRAydEyTIguEW6VRuAKjnaO/sOiR9BsSrOdXbD5Rhos/Xt7/mGUWbTOt/F+3W0/XLuDNmuYg1yIC/6hzkg44kgtdSTsQbOC9gWM7ayB4J4c=,
     *    sign_type=RSA,
     *    <br>enc=UTF-8
     * <br>}
     * </p>
     * @param params
     * @param alipayPublicKey 支付宝公钥
     * @param cusPrivateKey   商户私钥
     * @param isCheckSign     是否验签
     * @param isDecrypt       是否解密
     * @return 解密后明文，验签失败则异常抛出
     * @throws RuntimeException 
     */
    public static String checkSignAndDecrypt(Map<String, String> params, String alipayPublicKey,
                                             String cusPrivateKey, boolean isCheckSign,
                                             boolean isDecrypt) throws RuntimeException {
        String charset = params.get("enc");
        String bizContent = params.get("biz_content");
        if (isCheckSign) {
            if (!rsaCheckV2(params, alipayPublicKey, charset)) {
                throw new RuntimeException("rsaCheck failure:rsaParams=" + params);
            }
        }

        if (isDecrypt) {
            return rsaDecrypt(bizContent, cusPrivateKey, charset);
        }

        return bizContent;
    }
    
    /**
     * 验签并解密
     * <p>
     * <b>目前适用于公众号</b><br>
     * params参数示例：
     * <br>{
     *    <br>biz_content=M0qGiGz+8kIpxe8aF4geWJdBn0aBTuJRQItLHo9R7o5JGhpic/MIUjvXo2BLB++BbkSq2OsJCEQFDZ0zK5AJYwvBgeRX30gvEj6eXqXRt16/IkB9HzAccEqKmRHrZJ7PjQWE0KfvDAHsJqFIeMvEYk1Zei2QkwSQPlso7K0oheo/iT+HYE8aTATnkqD/ByD9iNDtGg38pCa2xnnns63abKsKoV8h0DfHWgPH62urGY7Pye3r9FCOXA2Ykm8X4/Bl1bWFN/PFCEJHWe/HXj8KJKjWMO6ttsoV0xRGfeyUO8agu6t587Dl5ux5zD/s8Lbg5QXygaOwo3Fz1G8EqmGhi4+soEIQb8DBYanQOS3X+m46tVqBGMw8Oe+hsyIMpsjwF4HaPKMr37zpW3fe7xOMuimbZ0wq53YP/jhQv6XWodjT3mL0H5ACqcsSn727B5ztquzCPiwrqyjUHjJQQefFTzOse8snaWNQTUsQS7aLsHq0FveGpSBYORyA90qPdiTjXIkVP7mAiYiAIWW9pCEC7F3XtViKTZ8FRMM9ySicfuAlf3jtap6v2KPMtQv70X+hlmzO/IXB6W0Ep8DovkF5rB4r/BJYJLw/6AS0LZM9w5JfnAZhfGM2rKzpfNsgpOgEZS1WleG4I2hoQC0nxg9IcP0Hs+nWIPkEUcYNaiXqeBc=,
     *    <br>sign=rlqgA8O+RzHBVYLyHmrbODVSANWPXf3pSrr82OCO/bm3upZiXSYrX5fZr6UBmG6BZRAydEyTIguEW6VRuAKjnaO/sOiR9BsSrOdXbD5Rhos/Xt7/mGUWbTOt/F+3W0/XLuDNmuYg1yIC/6hzkg44kgtdSTsQbOC9gWM7ayB4J4c=,
     *    sign_type=RSA,
     *    <br>enc=UTF-8
     * <br>}
     * </p>
     * @param params
     * @param alipayPublicKey 支付宝公钥
     * @param cusPrivateKey   商户私钥
     * @param isCheckSign     是否验签
     * @param isDecrypt       是否解密
     * @return 解密后明文，验签失败则异常抛出
     * @throws RuntimeException 
     */
    public static String checkSignAndDecrypt(Map<String, String> params, String alipayPublicKey,
                                             String cusPrivateKey, boolean isCheckSign,
                                             boolean isDecrypt, String signType) throws RuntimeException {
        String charset = params.get("enc");
        String bizContent = params.get("biz_content");
        if (isCheckSign) {
            if (!rsaCheckV2(params, alipayPublicKey, charset,signType)) {
                throw new RuntimeException("rsaCheck failure:rsaParams=" + params);
            }
        }

        if (isDecrypt) {
            return rsaDecrypt(bizContent, cusPrivateKey, charset);
        }

        return bizContent;
    }

    /**
     * 加密并签名<br>
     * <b>目前适用于公众号</b>
     * @param bizContent      待加密、签名内容
     * @param alipayPublicKey 支付宝公钥
     * @param cusPrivateKey   商户私钥
     * @param enc         字符集，如UTF-8, GBK, GB2312
     * @param isEncrypt       是否加密，true-加密  false-不加密
     * @param isSign          是否签名，true-签名  false-不签名
     * @return 加密、签名后xml内容字符串
     * <p>
     * 返回示例：
     * <alipay>
     *  <response>密文</response>
     *  <encryption_type>RSA</encryption_type>
     *  <sign>sign</sign>
     *  <sign_type>RSA</sign_type>
     * </alipay>
     * </p>
     * @throws RuntimeException 
     */
    public static String encryptAndSign(String bizContent, String alipayPublicKey,
                                        String cusPrivateKey, String charset, boolean isEncrypt,
                                        boolean isSign) throws RuntimeException {
        StringBuilder sb = new StringBuilder();
        if (Strings.isEmpty(charset)) {
            charset = "GBK";
        }
        sb.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>");
        if (isEncrypt) {// 加密
            sb.append("<alipay>");
            String encrypted = rsaEncrypt(bizContent, alipayPublicKey, charset);
            sb.append("<response>" + encrypted + "</response>");
            sb.append("<encryption_type>RSA</encryption_type>");
            if (isSign) {
                String sign = rsaSign(encrypted, cusPrivateKey, charset);
                sb.append("<sign>" + sign + "</sign>");
                sb.append("<sign_type>RSA</sign_type>");
            }
            sb.append("</alipay>");
        } else if (isSign) {// 不加密，但需要签名
            sb.append("<alipay>");
            sb.append("<response>" + bizContent + "</response>");
            String sign = rsaSign(bizContent, cusPrivateKey, charset);
            sb.append("<sign>" + sign + "</sign>");
            sb.append("<sign_type>RSA</sign_type>");
            sb.append("</alipay>");
        } else {// 不加密，不加签
            sb.append(bizContent);
        }
        return sb.toString();
    }
    
    /**
     * 加密并签名<br>
     * <b>目前适用于公众号</b>
     * @param bizContent      待加密、签名内容
     * @param alipayPublicKey 支付宝公钥
     * @param cusPrivateKey   商户私钥
     * @param enc         字符集，如UTF-8, GBK, GB2312
     * @param isEncrypt       是否加密，true-加密  false-不加密
     * @param isSign          是否签名，true-签名  false-不签名
     * @return 加密、签名后xml内容字符串
     * <p>
     * 返回示例：
     * <alipay>
     *  <response>密文</response>
     *  <encryption_type>RSA</encryption_type>
     *  <sign>sign</sign>
     *  <sign_type>RSA</sign_type>
     * </alipay>
     * </p>
     * @throws RuntimeException 
     */
    public static String encryptAndSign(String bizContent, String alipayPublicKey,
                                        String cusPrivateKey, String charset, boolean isEncrypt,
                                        boolean isSign,String signType) throws RuntimeException {
        StringBuilder sb = new StringBuilder();
        if (Strings.isEmpty(charset)) {
            charset = "GBK";
        }
        sb.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>");
        if (isEncrypt) {// 加密
            sb.append("<alipay>");
            String encrypted = rsaEncrypt(bizContent, alipayPublicKey, charset);
            sb.append("<response>" + encrypted + "</response>");
            sb.append("<encryption_type>RSA</encryption_type>");
            if (isSign) {
                String sign = rsaSign(encrypted, cusPrivateKey, charset, signType);
                sb.append("<sign>" + sign + "</sign>");
                sb.append("<sign_type>");
                sb.append(signType);
                sb.append("</sign_type>");
            }
            sb.append("</alipay>");
        } else if (isSign) {// 不加密，但需要签名
            sb.append("<alipay>");
            sb.append("<response>" + bizContent + "</response>");
            String sign = rsaSign(bizContent, cusPrivateKey, charset, signType);
            sb.append("<sign>" + sign + "</sign>");
            sb.append("<sign_type>");
            sb.append(signType);
            sb.append("</sign_type>");
            sb.append("</alipay>");
        } else {// 不加密，不加签
            sb.append(bizContent);
        }
        return sb.toString();
    }

    /**
     * 公钥加密
     * 
     * @param content   待加密内容
     * @param publicKey 公钥
     * @param enc   字符集，如UTF-8, GBK, GB2312
     * @return 密文内容
     * @throws RuntimeException
     */
    public static String rsaEncrypt(String content, String publicKey,
                                    String charset) throws RuntimeException {
        try {
            PublicKey pubKey = getPublicKeyFromX509("RSA",
                new ByteArrayInputStream(publicKey.getBytes()));
            Cipher cipher = Cipher.getInstance("RSA");
            cipher.init(Cipher.ENCRYPT_MODE, pubKey);
            byte[] data = Strings.isEmpty(charset) ? content.getBytes()
                : content.getBytes(charset);
            int inputLen = data.length;
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            int offSet = 0;
            byte[] cache;
            int i = 0;
            // 对数据分段加密  
            while (inputLen - offSet > 0) {
                if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
                    cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK);
                } else {
                    cache = cipher.doFinal(data, offSet, inputLen - offSet);
                }
                out.write(cache, 0, cache.length);
                i++;
                offSet = i * MAX_ENCRYPT_BLOCK;
            }
            byte[] encryptedData = Base64.getEncoder().encode(out.toByteArray());
            out.close();

            return Strings.isEmpty(charset) ? new String(encryptedData)
                : new String(encryptedData, charset);
        } catch (Exception e) {
            throw new RuntimeException("EncryptContent = " + content + ",enc = " + charset,
                e);
        }
    }

    /**
     * 私钥解密
     * 
     * @param content    待解密内容
     * @param privateKey 私钥
     * @param enc    字符集，如UTF-8, GBK, GB2312
     * @return 明文内容
     * @throws RuntimeException
     */
    public static String rsaDecrypt(String content, String privateKey,
                                    String charset) throws RuntimeException {
        try {
            PrivateKey priKey = getPrivateKeyFromPKCS8("RSA",
                new ByteArrayInputStream(privateKey.getBytes()));
            Cipher cipher = Cipher.getInstance("RSA");
            cipher.init(Cipher.DECRYPT_MODE, priKey);
            byte[] encryptedData = Strings.isEmpty(charset)
                ? Base64.getDecoder().decode(content.getBytes())
                : Base64.getDecoder().decode(content.getBytes(charset));
            int inputLen = encryptedData.length;
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            int offSet = 0;
            byte[] cache;
            int i = 0;
            // 对数据分段解密  
            while (inputLen - offSet > 0) {
                if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
                    cache = cipher.doFinal(encryptedData, offSet, MAX_DECRYPT_BLOCK);
                } else {
                    cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet);
                }
                out.write(cache, 0, cache.length);
                i++;
                offSet = i * MAX_DECRYPT_BLOCK;
            }
            byte[] decryptedData = out.toByteArray();
            out.close();

            return Strings.isEmpty(charset) ? new String(decryptedData)
                : new String(decryptedData, charset);
        } catch (Exception e) {
            throw new RuntimeException("EncodeContent = " + content + ",enc = " + charset, e);
        }
    }

    /*
     * 计算MD5+BASE64
     */
    public static String md5Base64(String s) {
        if (s == null)
            return null;
        String encodeStr = "";
        byte[] utfBytes = s.getBytes();
        MessageDigest mdTemp;
        try {
            mdTemp = MessageDigest.getInstance("MD5");
            mdTemp.update(utfBytes);
            byte[] md5Bytes = mdTemp.digest();
            encodeStr = Base64.getEncoder().encodeToString(md5Bytes);
        } catch (Exception e) {
            throw new Error("Failed to generate MD5 : " + e.getMessage());
        }
        return encodeStr;
    }
    /*
     * 计算 HMAC-SHA1
     */
    public static String hmacsha1(String data, String key) {
        String result;
        try {
            SecretKeySpec signingKey = new SecretKeySpec(key.getBytes(), "HmacSHA1");
            Mac mac = Mac.getInstance("HmacSHA1");
            mac.init(signingKey);
            byte[] rawHmac = mac.doFinal(data.getBytes());
            result = Base64.getEncoder().encodeToString(rawHmac);
        } catch (Exception e) {
            throw new Error("Failed to generate HMAC : " + e.getMessage());
        }
        return result;
    }
    /*
     * 等同于javaScript中的 new Date().toUTCString();
     */
    public static String toGMTString(Date date) {
        SimpleDateFormat df = new SimpleDateFormat("E, dd MMM yyyy HH:mm:ss z", Locale.US);
        df.setTimeZone(new java.util.SimpleTimeZone(0, "GMT"));
        return df.format(date);
    }
    
//    public static final String ISO8601_DATE_FORMAT="yyyy-MM-dd'T'HH:mm:ss'Z'";
    /**
	 * 生成当前UTC时间戳
	 *
	 * @return
	 */
//	public static String generateTimestamp() {
//		Date date = new Date(System.currentTimeMillis());
//		SimpleDateFormat df = new SimpleDateFormat(ISO8601_DATE_FORMAT);
//		df.setTimeZone(new SimpleTimeZone(0, "GMT"));
//		return df.format(date);
//	}
	
	private static final Object LOCK = new Object();
	
	public static String hmacSHA1Signature(String key, String data) {
        try {
            byte[] signData = sign(key.getBytes("UTF-8"), data.getBytes("UTF-8"), null,
                                LOCK, "HmacSHA1");
            return new String(Base64.getEncoder().encode(signData));
        } catch (UnsupportedEncodingException ex) {
            throw new RuntimeException("Unsupported algorithm: " + "UTF-8", ex);
        }
    }
	
	public static byte[] sign(byte[] key, byte[] data, Mac macInstance, Object lock, String algorithm) {
        try {
            if (macInstance == null) {
                synchronized (lock) {
                    if (macInstance == null) {
                        macInstance = Mac.getInstance(algorithm);
                    }
                }
            }

            Mac mac;
            try {
                mac = (Mac) macInstance.clone();
            } catch (CloneNotSupportedException e) {
                // If it is not clonable, create a new one.
                mac = Mac.getInstance(algorithm);
            }
            mac.init(new SecretKeySpec(key, algorithm));
            return mac.doFinal(data);
        } catch (NoSuchAlgorithmException ex) {
            throw new RuntimeException("Unsupported algorithm: " + algorithm, ex);
        } catch (InvalidKeyException ex) {
            throw new RuntimeException("Invalid key: " + key, ex);
        }
    }
	
	public static void main(String[] args) {
		JsonObject p=Json.parse("{\"gmt_create\":\"2,0,2,4,-,0,6,-,2,9, ,1,8,:,1,0,:,3,6\",\"charset\":\"u,t,f,-,8\",\"seller_email\":\"z,h,u,c,e,@,c,h,i,n,e,s,e,p,s,y,.,o,r,g\",\"subject\":\"¹«,Ӧ,½²,ع, ,ф,m,Σ,»𹇬Ԥ\",\"sign\":\"b,c,F,8,S,H,k,T,a,Y,D,K,X,I,e,Q,f,f,0,E,e,x,C,R,m,M,r,e,m,u,7,3,o,/,M,2,Y,M,g,A,F,w,h,s,h,j,5,T,7,L,3,W,d,9,n,x,k,G,T,B,Z,5,0,b,b,y,5,X,B,O,h,j,N,i,O,C,O,W,L,k,+,c,g,V,I,C,c,A,J,h,C,T,Z,P,V,w,h,N,l,W,d,k,i,Q,Y,4,N,C,E,O,P,x,s,j,S,N,B,1,r,T,5,l,6,7,N,O,O,n,q,4,D,Y,I,p,x,S,7,Z,G,u,0,g,D,K,b,f,A,/,J,X,a,p,A,O,Y,q,v,w,8,4,+,d,m,p,+,f,4,P,R,3,1,N,+,q,A,l,2,b,C,Q,W,x,M,B,F,X,T,J,Z,p,P,H,m,r,V,e,N,S,s,4,M,o,e,i,0,i,4,y,V,A,R,Q,I,I,Y,T,+,7,q,s,K,1,G,8,I,S,m,h,7,q,k,v,Q,9,Z,U,4,h,4,5,s,Y,l,g,c,0,/,y,+,o,a,W,W,d,I,l,N,e,q,l,1,n,g,X,Q,z,J,R,I,d,b,p,G,C,k,B,q,1,M,P,c,S,3,2,W,S,y,A,W,0,S,y,o,N,i,1,f,R,N,4,J,4,Y,m,Q,G,g,G,R,I,/,M,x,E,g,u,o,t,O,A,s,E,K,T,J,Z,i,T,g,G,g,r,N,M,l,1,D,X,P,T,7,w,=,=\",\"body\":\"¹«,Ӧ,½²,ع, ,ф,m,Σ,»𹇬Ԥ\",\"buyer_id\":\"2,0,8,8,7,0,2,2,6,8,0,1,2,6,0,4\",\"invoice_amount\":\"0,.,0,1\",\"notify_id\":\"2,0,2,4,0,6,2,9,0,1,2,2,2,1,8,1,0,3,7,0,1,2,6,0,1,4,0,5,3,7,9,2,5,9\",\"fund_bill_list\":\"[,{,\\\",a,m,o,u,n,t,\\\",:,\\\",0,.,0,1,\\\",,,\\\",f,u,n,d,C,h,a,n,n,e,l,\\\",:,\\\",A,L,I,P,A,Y,A,C,C,O,U,N,T,\\\",},]\",\"notify_type\":\"t,r,a,d,e,_,s,t,a,t,u,s,_,s,y,n,c\",\"trade_status\":\"T,R,A,D,E,_,S,U,C,C,E,S,S\",\"receipt_amount\":\"0,.,0,1\",\"buyer_pay_amount\":\"0,.,0,1\",\"app_id\":\"2,0,1,7,0,8,2,2,0,8,3,2,4,2,2,7\",\"sign_type\":\"R,S,A,2\",\"seller_id\":\"2,0,8,8,8,1,1,9,6,3,9,6,9,4,8,2\",\"gmt_payment\":\"2,0,2,4,-,0,6,-,2,9, ,1,8,:,1,0,:,3,7\",\"notify_time\":\"2,0,2,4,-,0,6,-,2,9, ,1,8,:,1,0,:,3,7\",\"version\":\"1,.,0\",\"out_trade_no\":\"2,0,2,4,0,6,2,9,1,8,0,2,1,2,1,5,6,1,2\",\"total_amount\":\"0,.,0,1\",\"trade_no\":\"2,0,2,4,0,6,2,9,2,2,0,0,1,4,1,2,6,0,1,4,1,8,7,7,2,5,4,8\",\"auth_app_id\":\"2,0,1,7,0,8,2,2,0,8,3,2,4,2,2,7\",\"buyer_logon_id\":\"a,l,o,*,*,*,@,s,i,n,a,.,c,o,m\",\"point_amount\":\"0,.,0,0\"}", JsonObject.class);
		Map<String,String> m2=new HashMap<String, String>();
		p.forEach((k,v)->{
			m2.put(k,v.toString());
		});
		System.out.println(m2);
		var a=rsaCheckV1(m2,"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApmwCeSxwXfq/Dm2pWxrq7TcrIeBzNztf+pdicRZOu8bWDXB+0CsXG6BM2gnPfcRWh0/BPdTmVLEVL1FFi9MzUBjSHiUhLxcuD1/ImR3DQXMf6YntmRr2qGs9xnaMoe6urmf3ri94qeKMloNrmublJwULY8/fUp8A6DFBNEgMJeIUMjtQGtVC+NLzMJuAbqQ7cMDSFmvVDadmW8oPUJXTtmEL5TRjDSLN8dheIiTT0+TVxUvke1ViZDjRmgXpwm7x341ECWLOuWedQ+vrzWnhiP9I8vkI5o+DoA4DGzZgpPtkhl+G3jm26hOAsakiICSC6p0zL/sfd7D8KTcknQTFvQIDAQAB",
				"utf-8", "RSA2");
		System.out.println(a);
	}
}
